/*
University of Pernambuco - UPE
ECOMP - Computer Enginerring Department
 */

package hidra.metaheuristics.mopsodfrv3;

import hidra.core.population.HIDRAPopulationAlgorithm;
import hidra.gui.PlotManager;
import hidra.jmetal.core.Operator;
import hidra.jmetal.core.Problem;
import hidra.jmetal.core.Solution;
import hidra.jmetal.core.SolutionSet;
import hidra.jmetal.core.Variable;
import hidra.metaheuristics.mopsocdr.util.ParetoFrontUtil;
import hidra.metaheuristics.mopsocdr.util.RoletteWheelSelection;
import hidra.metaheuristics.mopsocdr.util.TournamentSelection;
import hidra.metaheuristics.mopsocdr.util.TurbulenceMutation;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;

import org.jfree.chart.ChartPanel;

import jmetal.util.Distance;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;
import jmetal.util.archive.CrowdingArchive;
import jmetal.util.archive.DiversityFactorArchive;
import jmetal.util.comparators.CrowdingDistanceComparator;
import jmetal.util.comparators.DiversityFactorComparator;
import jmetal.util.comparators.DominanceComparator;
import jmetal.util.wrapper.XReal;

/**
 * @author Dennis Cunha
 * @email  drcs@ecomp.poli.br
 */

public class MOPSODFRV3 extends HIDRAPopulationAlgorithm{

	private int swarmSize_;
	private int archiveSize_;
	private double[][] speed_;
	private double C1_;
	private double C2_;
	private double C3_;
	private double wMax_;
	private double wMin_;
	private double EFactor_;

	private SolutionSet particles_;
	private Solution[] best_;
	//private DiversityFactorArchive leaders_;
	private CrowdingArchive leaders_;

	private Comparator dominance_;
	private Comparator diversityFactorComparator_;
	private Comparator crowdingDistanceComparator_;
	private Distance distance_;
	private Operator turbulence_;	
	private RoletteWheelSelection rollete;
	private TournamentSelection tournament;

	private static final double MUTATION_PERCENTUAL_MOPSO = 0.5;

	private PlotManager plot_manager;

	public MOPSODFRV3(Problem problem) {                
		super (problem);

		this.plot_manager = new PlotManager();
		this.plot_manager.setupPlotExternalArchive();
		this.plot_manager.setupPlotSwarm();
	}

	public void initParams() {

		swarmSize_ = ((Integer) getInputParameter("swarmSize")).intValue();
		archiveSize_ = ((Integer) getInputParameter("archiveSize")).intValue();
		maxIterations_ = ((Integer) getInputParameter("maxIterations")).intValue();

		this.rollete = new RoletteWheelSelection();
		this.tournament = new TournamentSelection();

		iteration_ = 0 ;

		particles_ = new SolutionSet(swarmSize_);
		best_ = new Solution[swarmSize_];
		leaders_ = new CrowdingArchive(archiveSize_, problem_.getNumberOfObjectives());

		// Create comparators for dominance and crowding distance
		dominance_ = new DominanceComparator();
		diversityFactorComparator_ = new DiversityFactorComparator();
		crowdingDistanceComparator_ = new CrowdingDistanceComparator();
		distance_ = new Distance();

		// Create the speed vector
		speed_ = new double[swarmSize_][problem_.getNumberOfVariables()];
		HashMap<String, Object> parameters = new HashMap<String, Object>();

		parameters.put("maxIterations",maxIterations_);
		parameters.put("percMutation", MUTATION_PERCENTUAL_MOPSO);

		this.turbulence_ = new TurbulenceMutation(parameters);

		/* V1 
		this.C1_ = 1.49445;
		this.C2_ = 1.49445;
		this.C3_ = 1.49445; */

		/* V2 */
		this.C1_ = 0.9963;
		this.C2_ = 0.9963;
		this.C3_ = 0.9963; 

		/* V3 
		this.C1_ = 1.49445;
		this.C2_ = 0.74722;
		this.C3_ = 0.74722; */

		/* V4 
		this.C1_ = 1.49445;
		this.C2_ = 0.96966;
		this.C3_ = 0.48483; */

		/* V5 
		this.C1_ = 1.49445;
		this.C2_ = 0.48483;
		this.C3_ = 0.96966; */

		/* V6 
		this.C1_ = 1.49445;
		this.C2_ = 1.93932;
		this.C3_ = 0.96966; */

		/* V7
		this.C1_ = 1.49445;
		this.C2_ = 0.96966;
		this.C3_ = 1.93932; */

		/* V8 
		this.C1_ = 1.49445;
		this.C2_ = 0.0;
		this.C3_ = 1.49445; */ 

		this.wMax_ = 0.4;
		this.wMin_ = 0.0;

	}

	@Override
	protected SolutionSet getParetoFront() {
		return this.leaders_;
	}

	@Override
	protected SolutionSet initializationAlgorithm()
			throws ClassNotFoundException, JMException {
		initParams();

		//->Step 1 (and 3) Create the initial population and evaluate
		for (int i = 0; i < swarmSize_; i++){
			Solution particle = new Solution(problem_);
			problem_.evaluate(particle);
			problem_.evaluateConstraints(particle);
			particles_.add(particle);
		}

		//-> Step2. Initialize the speed_ of each particle to 0
		for (int i = 0; i < swarmSize_; i++) {
			for (int j = 0; j < problem_.getNumberOfVariables(); j++) {
				speed_[i][j] = 0.0;
			}
		}

		// Step4 and 5   
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		//-> Step 6. Initialize the memory of each particle
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			best_[i] = particle;
		}

		//Crowding the leaders_
		ParetoFrontUtil.crowdingDistanceAssignmentToMOPSOCDR(leaders_, problem_.getNumberOfObjectives());
		ParetoFrontUtil.diversityFactorAssignmentToMOPSODFR(leaders_);

		return this.leaders_;

	}

	@Override
	protected SolutionSet runIteration() throws JMException {

		computeSpeed();

		System.out.println(iteration_);

		//Compute the new positions for the particles_            
		computeNewPositions();

		//Mutate the particles_          
		mopsoMutation();

		//Evaluate the new particles_ in new positions
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = particles_.get(i);
			problem_.evaluate(particle);
			problem_.evaluateConstraints(particle);
		}

		//Actualize the archive          
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		//Assign crowding distance to the leaders_
		ParetoFrontUtil.crowdingDistanceAssignmentToMOPSOCDR(leaders_,
				problem_.getNumberOfObjectives());
		ParetoFrontUtil.diversityFactorAssignmentToMOPSODFR(leaders_);

		iteration_++;

		//Update Particles of the Memory
		for (int i = 0; i < particles_.size(); i++) {

			int flag = dominance_.compare(particles_.get(i), best_[i]);
			if (flag == -1) { // the new particle is best than the older pBest
				Solution particle = new Solution(particles_.get(i));
				best_[i] = particle;
			} else if(flag == 0){

				Solution solNearParticle = 
						ParetoFrontUtil.getNearSolution(this.leaders_, particles_.get(i));

				Solution solNearPBest = 
						ParetoFrontUtil.getNearSolution(this.leaders_, best_[i]);

				if(solNearParticle.getDiversityFactor() > solNearPBest.getDiversityFactor()){
					best_[i] = new Solution(particles_.get(i));
				}
			}
		}

		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		plot_manager.plotExternalArchive(this.leaders_, iteration_ + 1);
		plot_manager.plotSwarm(this.particles_, iteration_ + 1);

		return this.leaders_;

	}

	// Adaptive inertia 
	private double inertiaWeight() {    	
		double w = wMax_ - (((wMax_-wMin_)*(double)iteration_)/(double)maxIterations_);
		if ( w < 0.0 ) {
			w = 0.0;          
		}
		return w;
	} // inertiaWeight

	private void computeSpeed() throws JMException {
		double r1, r2, r3;
		double wmax, wmin, W;
		XReal bestGlobalDF;
		XReal bestGlobalCD;


		wmax = wMax_;
		wmin = wMin_;

		W = inertiaWeight();
		double summatoryDF = 0.0;
		double summatoryCD = 0.0;

		SolutionSet frontDF = new SolutionSet(leaders_.size());
		SolutionSet frontCD = new SolutionSet(leaders_.size());
		for (int i = 0; i < leaders_.size(); i++){
			frontDF.add(leaders_.get(i));
			frontCD.add(leaders_.get(i));
		}

		frontDF.sort(diversityFactorComparator_);
		frontCD.sort(crowdingDistanceComparator_);

		for (int i=0; i < frontDF.size() ;i++) {
			summatoryDF = summatoryDF + frontDF.get(i).getDiversityFactor();
			summatoryCD = summatoryCD + frontDF.get(i).getCrowdingDistance();
		}

		for (int i = 0; i < swarmSize_; i++) {
			XReal particle = new XReal(particles_.get(i)) ;
			XReal bestParticle = new XReal(best_[i]) ;

			//bestGlobalDF = new XReal(rollete.executeDF(frontDF, summatoryDF));
			//bestGlobalCD = new XReal(rollete.execute(frontCD, summatoryCD));
			
			bestGlobalDF = new XReal(tournament.executeDF(frontDF));
			bestGlobalCD = new XReal(tournament.executeCD(frontCD));

			for (int var = 0; var < particle.getNumberOfDecisionVariables(); var++) {

				//updateC2C3();

				// generate stochastic components for each dimension
				r1 = PseudoRandom.randDouble(0.0, 1.0);
				r2 = PseudoRandom.randDouble(0.0, 1.0);
				r3 = PseudoRandom.randDouble(0.0, 1.0);

				//Computing the velocity of this particle 
				speed_[i][var] =  ( W * speed_[i][var] + 
						C1_ * r1 * (bestParticle.getValue(var) - particle.getValue(var)) +
						C2_ * r2 * (bestGlobalCD.getValue(var) - particle.getValue(var)) +
						C3_ * r3 * (bestGlobalDF.getValue(var) - particle.getValue(var)) );

				double vmax = particle.getUpperBound(var) - particle.getLowerBound(var);

				speed_[i][var] = 
						Math.signum(speed_[i][var]) * 
						Math.min(Math.abs(speed_[i][var]),vmax);

			}
		}
	} // computeSpeed

	private void updateC2C3() {
		this.C2_ = 0.0 + (((1.49445)*(double)iteration_)/(double)maxIterations_);
		this.C3_ = 1.49445 - (((1.49445)*(double)iteration_)/(double)maxIterations_);
	}

	/**
	 * Apply a mutation operator to some particles in the swarm
	 * @throws JMException 
	 */
	private void mopsoMutation() throws JMException {

		turbulence_.setParameter("currentIteration", iteration_);

		for (int i = 0; i < particles_.size(); i++) {
			turbulence_.execute(particles_.get(i));
		}

	} // mopsoMutation


	/**
	 * Update the position of each particle
	 * @throws JMException 
	 */
	private void computeNewPositions() throws JMException{
		for (int i = 0; i < swarmSize_; i++){
			Variable[] particle = particles_.get(i).getDecisionVariables();
			//particle.move(speed_[i]);
			for (int var = 0; var < particle.length; var++){
				particle[var].setValue(particle[var].getValue()+ speed_[i][var]);
				if (particle[var].getValue() < problem_.getLowerLimit(var)){
					particle[var].setValue(problem_.getLowerLimit(var));                    
					speed_[i][var] = speed_[i][var] * -1.0;    
				}
				if (particle[var].getValue() > problem_.getUpperLimit(var)){
					particle[var].setValue(problem_.getUpperLimit(var));                    
					speed_[i][var] = speed_[i][var] * -1.0;
				}
			}
		}
	} // computeNewPositions

	public ChartPanel getChartPanelExternalArchive() {
		return plot_manager.getChartPanelExternalArchive();
	}

	public void setChartPanelExternalArchive (ChartPanel chartPanelExternalArchive) {
		this.plot_manager.setChartPanelExternalArchive(chartPanelExternalArchive);
	}

	public ChartPanel getChartPanelSwarm() {
		return plot_manager.getChartPanelSwarm();
	}

	public void getChartPanelSwarm(ChartPanel chartPanelSwarm) {
		this.plot_manager.setChartPanelSwarm(chartPanelSwarm);
	}

}
